﻿using System.IO;
using ProtoBuf.Meta;

namespace XchangeStreamer.Utilities
{
    public static class Compression<T>
    {
        #region Private Fields

        private static TypeModel compiledModel;

        #endregion

        #region Const.

        static Compression()
        {
            RuntimeTypeModel runTimeModel = TypeModel.Create();
            MetaType meta = runTimeModel.Add(typeof(T), false);
            var props = typeof(T).GetProperties();

            for (int i = 0; i < props.Length; i++)
            {
                meta.Add(i + 1, props[i].Name);
            }

            compiledModel = runTimeModel.Compile();
        }

        #endregion

        #region Public Methods

        public static byte Compress<K>(K entity, out byte[] compressed)
        {
            byte[] buffer = Serialize<K>(entity);
            if (buffer.Length <= Settings.CompressionThreshold)
            {
                compressed = buffer;
                return byte.MinValue;
            }

            compressed = Compress(buffer);
            return byte.MaxValue;
        }

        public static void Compress<K>(string path, K entity)
        {
            byte[] compressed;
            Compress<K>(entity, out compressed);
            File.WriteAllBytes(path, compressed);
        }

        public static K Decompress<K>(byte[] rawInfo, byte notCompressed)
        {
            byte[] buffer = null;
            if (notCompressed == byte.MinValue)
            {
                buffer = rawInfo;
            }
            else
            {
                buffer = Decompress(rawInfo);
            }

            return Desrialize<K>(buffer);
        }

        public static K Decompress<K>(string path)
        {
            return Decompress<K>(File.ReadAllBytes(path), byte.MaxValue);
        }

        #endregion

        #region Private Methods

        #region Serialization

        #region ProtoBuf

        private static byte[] Serialize<K>(K obj)
        {
            if (obj == null)
            {
                return null;
            }

            MemoryStream ms = new MemoryStream();
            compiledModel.Serialize(ms, obj);
            var serialized = ms.ToArray();
            ms.Close();
            ms.Dispose();

            return serialized;
        }

        private static K Desrialize<K>(byte[] arrBytes)
        {
            if (arrBytes == null)
            {
                return default(K);
            }

            MemoryStream ms = new MemoryStream(arrBytes);
            var deserialized = (K)compiledModel.Deserialize(ms, null, typeof(K));
            ms.Close();
            ms.Dispose();

            return deserialized;
        }

        #endregion

        #endregion

        #region Compression

        #region 7zip

        private static byte[] Compress(byte[] inputBytes)
        {
            return SevenZip.Compression.LZMA.SevenZipHelper.Compress(inputBytes);
        }

        private static byte[] Decompress(byte[] inputBytes)
        {
            return SevenZip.Compression.LZMA.SevenZipHelper.Decompress(inputBytes);
        }

        #endregion

        #endregion

        #endregion
    }
}